
#include <setjmp.h>
#include <cpu.h>
#include <dev.h>
#include "pentium3.h"
#include "exception.h"
#include "access.h"
#include "alu.h"

/* ------------------------------------------------------------------------- */

#define REG_AL						0
#define REG_CL						1
#define REG_DL						2
#define REG_BL						3
#define REG_AH						4
#define REG_CH						5
#define REG_DH						6
#define REG_BH						7

#define REG_AX						0
#define REG_CX						1
#define REG_DX						2
#define REG_BX						3
#define REG_SP						4
#define REG_BP						5
#define REG_SI						6
#define REG_DI						7

#define REG_EAX						0
#define REG_ECX						1
#define REG_EDX						2
#define REG_EBX						3
#define REG_ESP						4
#define REG_EBP						5
#define REG_ESI						6
#define REG_EDI						7

#define SEG_ES						0
#define SEG_CS						1
#define SEG_SS						2
#define SEG_DS						3
#define SEG_FS						4
#define SEG_GS						5

#define reg32(id)					(reg[(id) & 7])
#define reg16(id)					(*((uint16 *)(&reg32(id))))
#define reg8(id)					(*((uint8 *)(&reg32((id) & 3)) + (((id) >> 2) & 1)))

#define sreg2(id)					(sreg[(id) & 3])
#define sreg3(id)					(sreg[(id) & 7])
#define xsreg2(id)					(xsreg[(id) & 3])
#define xsreg3(id)					(xsreg[(id) & 7])

#define code_use32					((xcs & DESC_D) ? true : false)
#define stack_use32					((xss & DESC_D) ? true : false)
#define oper_use32					(code_use32 ^ oper_size)
#define addr_use32					(code_use32 ^ addr_size)

#define read_byte(seg, addr)		virtual_read(sreg3(seg), xsreg3(seg), (addr), 1)
#define read_word(seg, addr)		virtual_read(sreg3(seg), xsreg3(seg), (addr), 2)
#define read_dword(seg, addr)		virtual_read(sreg3(seg), xsreg3(seg), (addr), 4)
#define write_byte(seg, addr, b)	virtual_write(sreg3(seg), xsreg3(seg), (addr), (b), 1)
#define write_word(seg, addr, w)	virtual_write(sreg3(seg), xsreg3(seg), (addr), (w), 2)
#define write_dword(seg, addr, d)	virtual_write(sreg3(seg), xsreg3(seg), (addr), (d), 4)

#define make_dtr(limit, base)		(((reg48_t)(base) << 16) | (limit))

enum {
	PREFIX_NONE = -1,
	PREFIX_LOCK,
	PREFIX_REPNZ,
	PREFIX_REPZ,
	PREFIX_ES = SEG_ES,
	PREFIX_CS = SEG_CS,
	PREFIX_SS = SEG_SS,
	PREFIX_DS = SEG_DS,
	PREFIX_FS = SEG_FS,
	PREFIX_GS = SEG_GS,
};

/* ------------------------------------------------------------------------- */

static inline void fetch_code(void);
static inline uint execute(size_t size);
static inline uint32 process_sib(uint8 sib, uint8 mod);
static inline uint test_cond(uint cond);
static inline void push_word(uint16 data);
static inline void push_dword(uint32 data);
static inline uint16 pop_word(void);
static inline uint32 pop_dword(void);
static inline void relative_jump(int disp);
static inline void absolute_jump(uint addr);

static inline int8 process_group1_byte(uint op, int8 x, int8 y);
static inline int16 process_group1_word(uint op, int16 x, int16 y);
static inline int32 process_group1_dword(uint op, int32 x, int32 y);
static inline int8 process_group2_byte(uint op, int8 x, uint8 y);
static inline int16 process_group2_word(uint op, int16 x, uint8 y);
static inline int32 process_group2_dword(uint op, int32 x, uint8 y);
static inline int8 process_group3_byte(uint op, int8 x);
static inline int16 process_group3_word(uint op, int16 x);
static inline int32 process_group3_dword(uint op, int32 x);
static inline int8 process_group4_byte(uint op, int8 x);
static inline int16 process_group4_word(uint op, int16 x);
static inline int32 process_group4_dword(uint op, int32 x);
static inline int16 process_group8_word(uint op, uint16 x, uint8 y);
static inline int32 process_group8_dword(uint op, uint32 x, uint8 y);

static uint fastcall process_prefix(void);
static void fastcall process_ins(uint op);
static uint32 fastcall process_modrm(uint modrm);
static void fastcall process_ext_ins(uint op);
static void fastcall load_segment(int seg, uint16 sel);
static void fastcall load_task(uint16 sel);

static void process_intr(void);

/* ------------------------------------------------------------------------- */

reg32_t reg[8];
reg16_t sreg[6];
reg64_t xsreg[6];
reg32_t eflags, eip;
reg32_t crx[5];
reg32_t drx[8];
reg48_t gdtr, idtr;
reg16_t tr, ldtr;
reg64_t xtr, xldtr;
reg80_t stx[8];
reg32_t mxcsr;
reg128_t xmm[8];

/* ------------------------------------------------------------------------- */

static int lock_rep;
static int override;
static bool oper_size;
static bool addr_size;

static jmp_buf cpu_jmp;

/* ------------------------------------------------------------------------- */

void reset_piii(void)
{
	/* exceptions and interrupts */
	reset_exception();

	/* zeroes */
	eax = 0x00000000;
	ebx = 0x00000000;
	ecx = 0x00000000;
	esi = 0x00000000;
	edi = 0x00000000;
	ebp = 0x00000000;
	esp = 0x00000000;
	cr2 = 0x00000000;
	cr3 = 0x00000000;
	cr4 = 0x00000000;
	dr0 = 0x00000000;
	dr1 = 0x00000000;
	dr2 = 0x00000000;
	dr3 = 0x00000000;

	/* P6 family, Celeron (Coppermine) model */
	edx = 0x0000068a;

	/* boot point: F000:FFF0h (physical FFFFFFF0h) */
	cs = 0xf000;
	xcs = 0xff009bff0000ffff;
	eip = 0x0000fff0;

	/* other segments: 0000h */
	ds = 0x0000;
	xds = 0x000093000000ffff;
	es = 0x0000;
	xes = 0x000093000000ffff;
	ss = 0x0000;
	xss = 0x000093000000ffff;
	fs = 0x0000;
	xfs = 0x000093000000ffff;
	gs = 0x0000;
	xgs = 0x000093000000ffff;

	/* initial flags */
	eflags = 0x00000002;

	/* initial CR0 */
	cr0 = 0x60000010;

	/* initial DR6/DR7 */
	dr6 = 0xffff0ff0;
	dr7 = 0x00000400;

	/* initial MXCSR */
	mxcsr = 0x00001f80;

	/* initial GDTR/IDTR */
	gdtr = 0x00000000ffff;
	idtr = 0x00000000ffff;

	/* initial TR */
	tr = 0x0000;
	xtr = 0x000093000000ffff;

	/* initial LDTR */
	ldtr = 0x0000;
	xldtr = 0x000093000000ffff;

	/* initial float/MMX registers */
	st(0) = 0.0;
	st(1) = 0.0;
	st(2) = 0.0;
	st(3) = 0.0;
	st(4) = 0.0;
	st(5) = 0.0;
	st(6) = 0.0;
	st(7) = 0.0;

	/* initial SSE registers */
	memset(&xmm0, 0, sizeof(xmm0));
	memset(&xmm1, 0, sizeof(xmm1));
	memset(&xmm2, 0, sizeof(xmm2));
	memset(&xmm3, 0, sizeof(xmm3));
	memset(&xmm4, 0, sizeof(xmm4));
	memset(&xmm5, 0, sizeof(xmm5));
	memset(&xmm6, 0, sizeof(xmm6));
	memset(&xmm7, 0, sizeof(xmm7));

	/* initial MSR */
	tsc = 0x0000000000000000;
}

void start_piii(void)
{
	uint op;

	setjmp(cpu_jmp);

	while (1) {
		fetch_code();
		op = process_prefix();
		process_ins(op);
		process_intr();
	}
}

void resume_piii(void)
{
	longjmp(cpu_jmp, 0);
}

/* ------------------------------------------------------------------------- */

static inline void fetch_code(void)
{
	// TODO: not good
	tsc += 100;
}

static uint32 brk = 0x8001A230;//0x8001A286;

static inline uint execute(size_t size)
{
	uint code;

	if (eip == brk)
	{
		_asm int 3
		code = read_dword(SEG_SS, esp);
		if (0)
		raise_exception(EX_BP);
	}

	code = virtual_execute(cs, xcs, eip, size);
	eip += (uint32)size;

	return code;
}

static inline uint32 process_sib(uint8 sib, uint8 mod)
{
	uint8 scale = (sib & 0xc0) >> 6;
	uint32 addr;

	if (!mod && (sib & 0x7) == 0x5)
		addr = execute(4);
	else
		addr = reg32(sib);

	if ((sib & 0x38) != 0x20)
		addr += reg32(sib >> 3) << scale;

	return addr;
}

static inline uint test_cond(uint op)
{
	switch (op & 0x0f) {
	/* overflow */
	case 0x00:
		return get_flag(FLAG_OF);
	case 0x01:
		return !get_flag(FLAG_OF);
	/* below */
	case 0x02:
		return get_flag(FLAG_CF);
	case 0x03:
		return !get_flag(FLAG_CF);
	/* zero */
	case 0x04:
		return get_flag(FLAG_ZF);
	case 0x05:
		return !get_flag(FLAG_ZF);
	/* above */
	case 0x06:
		return get_flag(FLAG_CF) || get_flag(FLAG_ZF);
	case 0x07:
		return !get_flag(FLAG_CF) && !get_flag(FLAG_ZF);
	/* sign */
	case 0x08:
		return get_flag(FLAG_SF);
	case 0x09:
		return !get_flag(FLAG_SF);
	/* parity*/
	case 0x0a:
		return get_flag(FLAG_PF);
	case 0x0b:
		return !get_flag(FLAG_PF);
	/* less */
	case 0x0c:
		return !get_flag(FLAG_SF) != !get_flag(FLAG_OF);
	case 0x0d:
		return !get_flag(FLAG_SF) == !get_flag(FLAG_OF);
	/* great */
	case 0x0e:
		return get_flag(FLAG_ZF) || (!get_flag(FLAG_SF) != !get_flag(FLAG_OF));
	case 0x0f:
		return !get_flag(FLAG_ZF) && (!get_flag(FLAG_SF) == !get_flag(FLAG_OF));
	}

	/* prevent compiler warning */
	return 0;
}

static inline void push_word(uint16 data)
{
	if (stack_use32) {
		esp -= 2;
		write_word(SEG_SS, esp, data);
	} else {
		sp -= 2;
		write_word(SEG_SS, sp, data);
	}
}

static inline void push_dword(uint32 data)
{
	if (stack_use32) {
		esp -= 4;
		write_dword(SEG_SS, esp, data);
	} else {
		sp -= 4;
		write_dword(SEG_SS, sp, data);
	}
}

static inline uint16 pop_word(void)
{
	uint data;

	if (stack_use32) {
		data = read_word(SEG_SS, esp);
		esp += 2;
	} else {
		data = read_word(SEG_SS, sp);
		sp += 2;
	}

	return data;
}

static inline uint32 pop_dword(void)
{
	uint data;

	if (stack_use32) {
		data = read_dword(SEG_SS, esp);
		esp += 4;
	} else {
		data = read_dword(SEG_SS, sp);
		sp += 4;
	}

	return data;
}

static inline void relative_jump(int disp)
{
	eip += disp;
}

static inline void absolute_jump(uint addr)
{
	eip = addr;
}

static inline int8 process_group1_byte(uint op, int8 x, int8 y)
{
	switch (op & 0x07) {
	/* add */
	case 0x00:
		return alu_add_byte(x, y);
	/* or */
	case 0x01:
		return alu_or_byte(x, y);
	/* adc */
	case 0x02:
		return alu_adc_byte(x, y);
	/* sbb */
	case 0x03:
		return alu_sbb_byte(x, y);
	/* and */
	case 0x04:
		return alu_and_byte(x, y);
	/* sub */
	case 0x05:
		return alu_sub_byte(x, y);
	/* xor */
	case 0x06:
		return alu_xor_byte(x, y);
	/* cmp */
	case 0x07:
		alu_sub_byte(x, y);
		break;
	}

	return x;
}

static inline int16 process_group1_word(uint op, int16 x, int16 y)
{
	switch (op & 0x07) {
	/* add */
	case 0x00:
		return alu_add_word(x, y);
	/* or */
	case 0x01:
		return alu_or_word(x, y);
	/* adc */
	case 0x02:
		return alu_adc_word(x, y);
	/* sbb */
	case 0x03:
		return alu_sbb_word(x, y);
	/* and */
	case 0x04:
		return alu_and_word(x, y);
	/* sub */
	case 0x05:
		return alu_sub_word(x, y);
	/* xor */
	case 0x06:
		return alu_xor_word(x, y);
	/* cmp */
	case 0x07:
		alu_sub_word(x, y);
		break;
	}

	return x;
}

static inline int32 process_group1_dword(uint op, int32 x, int32 y)
{
	switch (op & 0x07) {
	/* add */
	case 0x00:
		return alu_add_dword(x, y);
	/* or */
	case 0x01:
		return alu_or_dword(x, y);
	/* adc */
	case 0x02:
		return alu_adc_dword(x, y);
	/* sbb */
	case 0x03:
		return alu_sbb_dword(x, y);
	/* and */
	case 0x04:
		return alu_and_dword(x, y);
	/* sub */
	case 0x05:
		return alu_sub_dword(x, y);
	/* xor */
	case 0x06:
		return alu_xor_dword(x, y);
	/* cmp */
	case 0x07:
		alu_sub_dword(x, y);
		break;
	}

	return x;
}

static inline int8 process_group2_byte(uint op, int8 x, uint8 y)
{
	switch (op & 0x07) {
	/* rol */
	case 0x00:
		return alu_rol_byte(x, y);
	/* ror */
	case 0x01:
		return alu_ror_byte(x, y);
	/* rcl */
	case 0x02:
		return alu_rcl_byte(x, y);
	/* rcr */
	case 0x03:
		return alu_rcr_byte(x, y);
	/* shl/sal */
	case 0x04:
		return alu_shl_byte(x, y);
	/* shr */
	case 0x05:
		return alu_shr_byte(x, y);
	/* sar */
	case 0x07:
		return alu_sar_byte(x, y);
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int16 process_group2_word(uint op, int16 x, uint8 y)
{
	switch (op & 0x07) {
	/* rol */
	case 0x00:
		return alu_rol_word(x, y);
	/* ror */
	case 0x01:
		return alu_ror_word(x, y);
	/* rcl */
	case 0x02:
		return alu_rcl_word(x, y);
	/* rcr */
	case 0x03:
		return alu_rcr_word(x, y);
	/* shl/sal */	   
	case 0x04:		   
		return alu_shl_word(x, y);
	/* shr */		   
	case 0x05:		   
		return alu_shr_word(x, y);
	/* sar */
	case 0x07:
		return alu_sar_word(x, y);
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int32 process_group2_dword(uint op, int32 x, uint8 y)
{
	switch (op & 0x07) {
	/* rol */
	case 0x00:
		return alu_rol_dword(x, y);
	/* ror */
	case 0x01:
		return alu_ror_dword(x, y);
	/* rcl */
	case 0x02:
		return alu_rcl_dword(x, y);
	/* rcr */
	case 0x03:
		return alu_rcr_dword(x, y);
	/* shl/sal */	   
	case 0x04:		   
		return alu_shl_dword(x, y);
	/* shr */		   
	case 0x05:		   
		return alu_shr_dword(x, y);
	/* sar */
	case 0x07:
		return alu_sar_dword(x, y);
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int8 process_group3_byte(uint op, int8 x)
{
	switch (op & 0x07) {
	/* test */
	case 0x00:
		alu_and_byte(x, execute(1));
		break;
	/* not */
	case 0x02:
		return alu_not_byte(x);
	/* neg */
	case 0x03:
		return alu_neg_byte(x);
	/* mul */
	case 0x04:
		al = alu_mul_byte(al, x, &ah);
		break;
	/* imul */
	case 0x05:
		al = alu_imul_byte(al, x, &ah);
		break;
	/* div */
	case 0x06:
		al = alu_div_byte(ax, x, &ah);
		break;
	/* idiv */
	case 0x07:
		al = alu_idiv_byte(ax, x, &ah);
		break;
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int16 process_group3_word(uint op, int16 x)
{
	uint32 tmp;

	switch (op & 0x07) {
	/* test */
	case 0x00:
		alu_and_word(x, execute(2));
		break;
	/* not */
	case 0x02:
		return alu_not_word(x);
	/* neg */
	case 0x03:
		return alu_neg_word(x);
	/* mul */
	case 0x04:
		ax = alu_mul_word(ax, x, &dx);
		break;
	/* imul */
	case 0x05:
		ax = alu_imul_word(ax, x, &dx);
		break;
	/* div */
	case 0x06:
		tmp = (uint16)ax | ((uint32)dx << 16);
		ax = alu_div_word(tmp, x, &dx);
		break;
	/* idiv */
	case 0x07:
		tmp = (uint16)ax | ((uint32)dx << 16);
		ax = alu_idiv_word(tmp, x, &dx);
		break;
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int32 process_group3_dword(uint op, int32 x)
{
	uint64 tmp;

	switch (op & 0x07) {
	/* test */
	case 0x00:
		alu_and_dword(x, execute(4));
		break;
	/* not */
	case 0x02:
		return alu_not_dword(x);
	/* neg */
	case 0x03:
		return alu_neg_dword(x);
	/* mul */
	case 0x04:
		eax = alu_mul_dword(eax, x, &edx);
		break;
	/* imul */
	case 0x05:
		eax = alu_imul_dword(eax, x, &edx);
		break;
	/* div */
	case 0x06:
		tmp = (uint32)eax | ((uint64)edx << 32);
		eax = alu_div_dword(tmp, x, &edx);
		break;
	/* idiv */
	case 0x07:
		tmp = (uint32)eax | ((uint64)edx << 32);
		eax = alu_idiv_dword(tmp, x, &edx);
		break;
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int8 process_group4_byte(uint op, int8 x)
{
	switch (op & 0x07) {
	/* inc */
	case 0x00:
		return alu_inc_byte(x);
	/* dec */
	case 0x01:
		return alu_dec_byte(x);
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int16 process_group4_word(uint op, int16 x)
{
	switch (op & 0x07) {
	/* inc */
	case 0x00:
		return alu_inc_word(x);
	/* dec */
	case 0x01:
		return alu_dec_word(x);
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int32 process_group4_dword(uint op, int32 x)
{
	switch (op & 0x07) {
	/* inc */
	case 0x00:
		return alu_inc_dword(x);
	/* dec */
	case 0x01:
		return alu_dec_dword(x);
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int16 process_group8_word(uint op, uint16 x, uint8 y)
{
	switch (op & 0x07) {
	/* bt */
	case 0x04:
		return alu_bt_word(x, y);
	/* bts */
	case 0x05:
		return alu_bts_word(x, y);
	/* btr */
	case 0x06:
		return alu_btr_word(x, y);
	/* btc */
	case 0x07:
		return alu_btc_word(x, y);
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}

	return x;
}

static inline int32 process_group8_dword(uint op, uint32 x, uint8 y)
{
	switch (op & 0x07) {
	/* bt */
	case 0x04:
		return alu_bt_dword(x, y);
	/* bts */
	case 0x05:
		return alu_bts_dword(x, y);
	/* btr */
	case 0x06:
		return alu_btr_dword(x, y);
	/* btc */
	case 0x07:
		return alu_btc_dword(x, y);
	/* undefined */
	default:
		raise_exception(EX_UD);
		break;
	}


	return x;
}

// The CS register only can be loaded with a selector for a code segment.

// Segment selectors for code segments that are not readable or for system segments cannot be loaded into data-segment registers (DS, ES, FS, and GS).

// Stack segments are data segments which must be read/write segments.
// Loading the SS register with a segment selector for a nonwritable data segment generates a general-protection exception (#GP).

// The real mode INT15 call specification described here is an Intel extension to an OEM BIOS.
// This extension allows an application to read and modify the contents of the microcode update
// data in NVRAM. The update loader, which is part of the system BIOS, cannot be updated by
// the interface. All of the functions defined in the specification must be implemented for a system
// to be considered compliant with the specification. The INT15 functions are accessible only
// from real mode.

static uint fastcall process_prefix(void)
{
	uint op;
	lock_rep = PREFIX_NONE;
	override = PREFIX_NONE;
	oper_size = false;
	addr_size = false;

	while (1) {

		op = execute(1);

		switch (op) {
		case 0xf0:
			lock_rep = PREFIX_LOCK;
			continue;
		case 0xf2:
			lock_rep = PREFIX_REPNZ;
			continue;
		case 0xf3:
			lock_rep = PREFIX_REPZ;
			continue;
		case 0x26:
			override = PREFIX_ES;
			continue;
		case 0x2e:
			override = PREFIX_CS;
			continue;
		case 0x36:
			override = PREFIX_SS;
			continue;
		case 0x3e:
			override = PREFIX_DS;
			continue;
		case 0x64:
			override = PREFIX_FS;
			continue;
		case 0x65:
			override = PREFIX_GS;
			continue;
		case 0x66:
			oper_size = true;
			continue;
		case 0x67:
			addr_size = true;
			continue;
		}

		return op;
	}
}

static void fastcall process_ins(uint op)
{
	int disp, offset, org, new;
	uint modrm, new_eip;
	uint32 addr, tmp;

	switch (op) {
	case 0x00:	case 0x08:	case 0x10:	case 0x18:	case 0x20:	case 0x28:	case 0x30:	case 0x38:
		/* Eb, Gb */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			reg8(modrm) = process_group1_byte(op >> 3, reg8(modrm), reg8(modrm >> 3));
		} else {
			addr = process_modrm(modrm);
			write_byte(override, addr, process_group1_byte(op >> 3, read_byte(override, addr), reg8(modrm >> 3)));
		}
		return;
	case 0x01:	case 0x09:	case 0x11:	case 0x19:	case 0x21:	case 0x29:	case 0x31:	case 0x39:
		/* Ev, Gv */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm) = process_group1_dword(op >> 3, reg32(modrm), reg32(modrm >> 3));
			else
				reg16(modrm) = process_group1_word(op >> 3, reg16(modrm), reg16(modrm >> 3));
		} else {
			addr = process_modrm(modrm);
			if (oper_use32)
				write_dword(override, addr, process_group1_dword(op >> 3, read_dword(override, addr), reg32(modrm >> 3)));
			else
				write_word(override, addr, process_group1_word(op >> 3, read_word(override, addr), reg16(modrm >> 3)));
		}
		return;
	case 0x02:	case 0x0a:	case 0x12:	case 0x1a:	case 0x22:	case 0x2a:	case 0x32:	case 0x3a:
		/* Gb, Eb */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0)
			reg8(modrm >> 3) = process_group1_byte(op >> 3, reg8(modrm >> 3), reg8(modrm));
		else
			reg8(modrm >> 3) = process_group1_byte(op >> 3, reg8(modrm >> 3), read_byte(override, process_modrm(modrm)));
		return;
	case 0x03:	case 0x0b:	case 0x13:	case 0x1b:	case 0x23:	case 0x2b:	case 0x33:	case 0x3b:
		/* Gv, Ev */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm >> 3) = process_group1_dword(op >> 3, reg32(modrm >> 3), reg32(modrm));
			else
				reg16(modrm >> 3) = process_group1_word(op >> 3, reg16(modrm >> 3), reg16(modrm));
		} else {
			addr = process_modrm(modrm);
			if (oper_use32)
				reg32(modrm >> 3) = process_group1_dword(op >> 3, reg32(modrm >> 3), read_dword(override, addr));
			else
				reg16(modrm >> 3) = process_group1_word(op >> 3, reg16(modrm >> 3), read_word(override, addr));
		}
		return;
	case 0x04:	case 0x0c:	case 0x14:	case 0x1c:	case 0x24:	case 0x2c:	case 0x34:	case 0x3c:
		/* AL, Ib */
		al = process_group1_byte(op >> 3, al, execute(1));
		return;
	case 0x05:	case 0x0d:	case 0x15:	case 0x1d:	case 0x25:	case 0x2d:	case 0x35:	case 0x3d:
		/* eAX, Iv */
		modrm = execute(1);
		if (oper_use32) {
			modrm |= execute(3) << 8;
			eax = process_group1_dword(op >> 3, eax, modrm);
		} else {
			modrm |= execute(1) << 8;
			ax = process_group1_word(op >> 3, ax, modrm);
		}
		return;
	case 0x06:	case 0x0e:	case 0x16:	case 0x1e:
		/* push segment register */
		push_dword(sreg2(op >> 3));
		return;
	case 0x07:	case 0x17:	case 0x1f:
		/* pop segment register */
		load_segment((op >> 3) & 3, pop_dword());
		return;
	case 0x0f:
		/* extended op code */
		process_ext_ins(execute(1));
		return;
	case 0x40:	case 0x41:	case 0x42:	case 0x43:	case 0x44:	case 0x45:	case 0x46:	case 0x47:
		/* inc (alternate encoding) */
		if (oper_use32)
			reg32(op) = alu_inc_dword(reg32(op));
		else
			reg16(op) = alu_inc_word(reg16(op));
		return;
	case 0x48:	case 0x49:	case 0x4a:	case 0x4b:	case 0x4c:	case 0x4d:	case 0x4e:	case 0x4f:
		/* dec (alternate encoding) */
		if (oper_use32)
			reg32(op) = alu_dec_dword(reg32(op));
		else
			reg16(op) = alu_dec_word(reg16(op));
		return;
	case 0x50:	case 0x51:	case 0x52:	case 0x53:	case 0x54:	case 0x55:	case 0x56:	case 0x57:
		/* push general register */
		if (oper_use32)
			push_dword(reg32(op));
		else
			push_word(reg32(op));
		return;
	case 0x58:	case 0x59:	case 0x5a:	case 0x5b:	case 0x5c:	case 0x5d:	case 0x5e:	case 0x5f:
		/* pop general register */
		if (oper_use32)
			reg32(op) = pop_dword();
		else
			reg16(op) = pop_word();
		return;
	case 0x60:
		/* pusha/pushad */
		tmp = esp;
		if (oper_use32) {
			push_dword(eax);
			push_dword(ecx);
			push_dword(edx);
			push_dword(ebx);
			push_dword(tmp);
			push_dword(ebp);
			push_dword(esi);
			push_dword(edi);
		} else {
			push_word(eax);
			push_word(ecx);
			push_word(edx);
			push_word(ebx);
			push_word(tmp);
			push_word(ebp);
			push_word(esi);
			push_word(edi);
		}
		return;
	case 0x61:
		/* popa/popad */
		if (oper_use32) {
			edi = pop_dword();
			esi = pop_dword();
			ebp = pop_dword();
			esp += 4;
			ebx = pop_dword();
			edx = pop_dword();
			ecx = pop_dword();
			eax = pop_dword();
		} else {
			di = pop_word();
			si = pop_word();
			bp = pop_word();
			esp += 2;
			bx = pop_word();
			dx = pop_word();
			cx = pop_word();
			ax = pop_word();
		}
		return;
	case 0x68:
		/* push immediate without sign-extension */
		if (oper_use32)
			push_dword(execute(4));
		else
			push_word(execute(2));
		return;
	case 0x6a:
		/* push immediate with sign-extension */
		if (oper_use32)
			push_dword((int8)execute(1));
		else
			push_word((int8)execute(1));
		return;
	case 0x6c:
		/* insb */
		check_port_access();
		if (addr_use32) {
			if (ecx || lock_rep != PREFIX_REPZ) {
				write_byte(PREFIX_ES, edi, in_byte(dx));
				if (get_flag(FLAG_DF))
					edi--;
				else
					edi++;
				if (lock_rep == PREFIX_REPZ) {
					ecx--;
					eip -= 2;
				}
			}
		} else {
			if (cx || lock_rep != PREFIX_REPZ) {
				write_byte(PREFIX_ES, di, in_byte(dx));
				if (get_flag(FLAG_DF))
					di--;
				else
					di++;
				if (lock_rep == PREFIX_REPZ) {
					cx--;
					eip -= 2;
				}
			}
		}
		return;
	case 0x6d:
		check_port_access();
		/* insd */
		if (oper_use32) {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					write_dword(PREFIX_ES, edi, in_dword(dx));
					if (get_flag(FLAG_DF))
						edi -= 4;
					else
						edi += 4;
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					write_dword(PREFIX_ES, di, in_dword(dx));
					if (get_flag(FLAG_DF))
						di -= 4;
					else
						di += 4;
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		/* insw */
		} else {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					write_word(PREFIX_ES, edi, in_word(dx));
					if (get_flag(FLAG_DF))
						edi -= 2;
					else
						edi += 2;
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					write_word(PREFIX_ES, di, in_word(dx));
					if (get_flag(FLAG_DF))
						di -= 2;
					else
						di += 2;
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		}
		return;
	case 0x6e:
		/* outsb */
		check_port_access();
		if (addr_use32) {
			if (ecx || lock_rep != PREFIX_REPZ) {
				out_byte(dx, read_byte(PREFIX_DS, esi));
				if (get_flag(FLAG_DF))
					esi--;
				else
					esi++;
				if (lock_rep == PREFIX_REPZ) {
					ecx--;
					eip -= 2;
				}
			}
		} else {
			if (cx || lock_rep != PREFIX_REPZ) {
				out_byte(dx, read_byte(PREFIX_DS, si));
				if (get_flag(FLAG_DF))
					si--;
				else
					si++;
				if (lock_rep == PREFIX_REPZ) {
					cx--;
					eip -= 2;
				}
			}
		}
		return;
	case 0x6f:
		check_port_access();
		/* outsd */
		if (oper_use32) {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					out_dword(dx, read_dword(PREFIX_DS, esi));
					if (get_flag(FLAG_DF))
						esi -= 4;
					else
						esi += 4;
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					out_dword(dx, read_dword(PREFIX_DS, si));
					if (get_flag(FLAG_DF))
						si -= 4;
					else
						si += 4;
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		/* outsw */
		} else {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					out_word(dx, read_word(PREFIX_DS, esi));
					if (get_flag(FLAG_DF))
						esi -= 2;
					else
						esi += 2;
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					out_word(dx, read_word(PREFIX_DS, si));
					if (get_flag(FLAG_DF))
						si -= 2;
					else
						si += 2;
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		}
		return;
	case 0x70:	case 0x71:	case 0x72:	case 0x73:	case 0x74:	case 0x75:	case 0x76:	case 0x77:
	case 0x78:	case 0x79:	case 0x7a:	case 0x7b:	case 0x7c:	case 0x7d:	case 0x7e:	case 0x7f:
		/* short jump with condition */
		offset = (int8)execute(1);
		if (test_cond(op))
			relative_jump(offset);
		return;
	case 0x80:	case 0x82:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			reg8(modrm) = process_group1_byte(modrm >> 3, reg8(modrm), execute(1));
		} else {
			addr = process_modrm(modrm);
			write_byte(override, addr, process_group1_byte(modrm >> 3, read_byte(override, addr), execute(1)));
		}
		return;
	case 0x81:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm) = process_group1_dword(modrm >> 3, reg32(modrm), execute(4));
			else
				reg16(modrm) = process_group1_word(modrm >> 3, reg16(modrm), execute(2));
		} else {
			addr = process_modrm(modrm);
			if (oper_use32)
				write_dword(override, addr, process_group1_dword(modrm >> 3, read_dword(override, addr), execute(4)));
			else
				write_word(override, addr, process_group1_word(modrm >> 3, read_word(override, addr), execute(2)));
		}
		return;
	case 0x83:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm) = process_group1_dword(modrm >> 3, reg32(modrm), (int8)execute(1));
			else
				reg16(modrm) = process_group1_word(modrm >> 3, reg16(modrm), (int8)execute(1));
		} else {
			addr = process_modrm(modrm);
			if (oper_use32)
				write_dword(override, addr, process_group1_dword(modrm >> 3, read_dword(override, addr), (int8)execute(1)));
			else
				write_word(override, addr, process_group1_word(modrm >> 3, read_word(override, addr), (int8)execute(1)));
		}
		return;
	case 0x84:
		/* test (alternate encoding) */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			alu_and_byte(reg8(modrm >> 3), reg8(modrm));
		} else {
			addr = process_modrm(modrm);
			alu_and_byte(read_byte(override, addr), reg8(modrm >> 3));
		}
		return;
	case 0x85:
		/* test (alternate encoding) */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				alu_and_dword(reg32(modrm >> 3), reg32(modrm));
			else
				alu_and_word(reg16(modrm >> 3), reg16(modrm));
		} else {
			addr = process_modrm(modrm);
			if (oper_use32)
				alu_and_dword(read_dword(override, addr), reg32(modrm >> 3));
			else
				alu_and_word(read_word(override, addr), reg16(modrm >> 3));
		}
		return;
	case 0x86:
		/* xchg */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			tmp = reg8(modrm);
			reg8(modrm) = reg8(modrm >> 3);
			reg8(modrm >> 3) = tmp;
		} else {
			addr = process_modrm(modrm);
			tmp = read_byte(override, addr);
			write_byte(override, addr, reg8(modrm >> 3));
			reg8(modrm >> 3) = tmp;
		}
		return;
	case 0x87:
		/* xchg */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32) {
				tmp = reg32(modrm);
				reg32(modrm) = reg32(modrm >> 3);
				reg32(modrm >> 3) = tmp;
			} else {
				tmp = reg16(modrm);
				reg16(modrm) = reg16(modrm >> 3);
				reg16(modrm >> 3) = tmp;
			}
		} else {
			if (oper_use32) {
				addr = process_modrm(modrm);
				tmp = read_dword(override, addr);
				write_dword(override, addr, reg32(modrm >> 3));
				reg32(modrm >> 3) = tmp;
			} else {
				addr = process_modrm(modrm);
				tmp = read_word(override, addr);
				write_word(override, addr, reg16(modrm >> 3));
				reg16(modrm >> 3) = tmp;
			}
		}
		return;
	case 0x88:
		/* move */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0)
			reg8(modrm) = reg8(modrm >> 3);
		else
			write_byte(override, process_modrm(modrm), reg8(modrm >> 3));
		return;
	case 0x89:
		/* move */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm) = reg32(modrm >> 3);
			else
				reg16(modrm) = reg16(modrm >> 3);
		} else {
			if (oper_use32)
				write_dword(override, process_modrm(modrm), reg32(modrm >> 3));
			else
				write_word(override, process_modrm(modrm), reg16(modrm >> 3));
		}
		return;
	case 0x8a:
		/* move */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0)
			reg8(modrm >> 3) = reg8(modrm);
		else
			reg8(modrm >> 3) = read_byte(override, process_modrm(modrm));
		return;
	case 0x8b:
		/* move */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm >> 3) = reg32(modrm);
			else
				reg16(modrm >> 3) = reg16(modrm);
		} else {
			if (oper_use32)
				reg32(modrm >> 3) = read_dword(override, process_modrm(modrm));
			else
				reg16(modrm >> 3) = read_word(override, process_modrm(modrm));
		}
		return;
	case 0x8c:
		/* move */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0)
			reg16(modrm) = sreg3(modrm >> 3);
		else
			write_word(override, process_modrm(modrm), sreg3(modrm >> 3));
		return;
	case 0x8d:
		/* lea */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			raise_exception(EX_UD);
		} else {
			addr = process_modrm(modrm);
			if (oper_use32)
				reg32(modrm >> 3) = addr;
			else
				reg16(modrm >> 3) = addr;
		}
		return;
	case 0x8e:
		/* move */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0)
			load_segment((modrm >> 3) & 7, reg16(modrm));
		else
			load_segment((modrm >> 3) & 7, read_word(override, process_modrm(modrm)));
		return;
	case 0x8f:
		modrm = execute(1);
		switch ((modrm >> 3) & 0x07) {
		case 0x00:
			/* pop */
			if ((modrm & 0xc0) == 0xc0) {
				if (oper_use32)
					reg32(modrm) = pop_dword();
				else
					reg16(modrm) = pop_word();
			} else {
				addr = process_modrm(modrm);
				if (oper_use32)
					write_dword(override, addr, pop_dword());
				else
					write_word(override, addr, pop_word());
			}
			return;
		}
		return;
	case 0x90:
		/* nop */
		return;
	case 0x9a:
		/* far call */
		if (oper_use32) {
			new_eip = execute(4);
			load_segment(SEG_CS, execute(2));
			push_dword(eip);
		} else {
			new_eip = execute(2);
			load_segment(SEG_CS, execute(2));
			push_word(eip);
		}
		absolute_jump(new_eip);
		return;
	case 0x9c:
		/* push flags register */
		check_eflags_access();
		if (oper_use32)
			push_dword(eflags & 0x00fcffff);
		else
			push_word(eflags);
		return;
	case 0x9d:
		/* pop flags register */
		check_eflags_access();
		// TODO: not exactly
		if (oper_use32)
			eflags = pop_dword();
		else
			flags = pop_word();
		return;
	case 0xa0:
		/* move */
		if (override == PREFIX_NONE)
			override = PREFIX_DS;
		al = read_byte(override, execute(addr_use32 ? 4 : 2));
		return;
	case 0xa1:
		/* move */
		if (override == PREFIX_NONE)
			override = PREFIX_DS;
		if (oper_use32)
			eax = read_dword(override, execute(addr_use32 ? 4 : 2));
		else
			ax = read_word(override, execute(addr_use32 ? 4 : 2));
		return;
	case 0xa2:
		/* move */
		if (override == PREFIX_NONE)
			override = PREFIX_DS;
		write_byte(override, execute(addr_use32 ? 4 : 2), al);
		return;
	case 0xa3:
		/* move */
		if (override == PREFIX_NONE)
			override = PREFIX_DS;
		if (oper_use32)
			write_dword(override, execute(addr_use32 ? 4 : 2), eax);
		else
			write_word(override, execute(addr_use32 ? 4 : 2), ax);
		return;
	case 0xa4:
		/* movsb */
		if (addr_use32) {
			if (ecx || lock_rep != PREFIX_REPZ) {
				write_byte(PREFIX_ES, edi, read_byte(PREFIX_DS, esi));
				if (get_flag(FLAG_DF)) {
					esi--;
					edi--;
				} else {
					esi++;
					edi++;
				}
				if (lock_rep == PREFIX_REPZ) {
					ecx--;
					eip -= 2;
				}
			}
		} else {
			if (cx || lock_rep != PREFIX_REPZ) {
				write_byte(PREFIX_ES, di, read_byte(PREFIX_DS, si));
				if (get_flag(FLAG_DF)) {
					si--;
					di--;
				} else {
					si++;
					di++;
				}
				if (lock_rep == PREFIX_REPZ) {
					cx--;
					eip -= 2;
				}
			}
		}
		return;
	case 0xa5:
		/* movsd */
		if (oper_use32) {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					write_dword(PREFIX_ES, edi, read_dword(PREFIX_DS, esi));
					if (get_flag(FLAG_DF)) {
						esi -= 4;
						edi -= 4;
					} else {
						esi += 4;
						edi += 4;
					}
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					write_dword(PREFIX_ES, di, read_dword(PREFIX_DS, si));
					if (get_flag(FLAG_DF)) {
						si -= 4;
						di -= 4;
					} else {
						si += 4;
						di += 4;
					}
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		/* movsw */
		} else {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					write_word(PREFIX_ES, edi, read_word(PREFIX_DS, esi));
					if (get_flag(FLAG_DF)) {
						esi -= 2;
						edi -= 2;
					} else {
						esi += 2;
						edi += 2;
					}
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					write_word(PREFIX_ES, di, read_word(PREFIX_DS, si));
					if (get_flag(FLAG_DF)) {
						si -= 2;
						di -= 2;
					} else {
						si += 2;
						di += 2;
					}
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		}
		return;
	case 0xa6:
		/* cmpsb */
		if (addr_use32) {
			if (ecx || (lock_rep != PREFIX_REPZ && lock_rep != PREFIX_REPNZ)) {
				alu_sub_byte(read_byte(PREFIX_DS, esi), read_byte(PREFIX_ES, edi));
				if (get_flag(FLAG_DF)) {
					esi--;
					edi--;
				} else {
					esi++;
					edi++;
				}
				if ((lock_rep == PREFIX_REPZ && get_flag(FLAG_ZF)) || (lock_rep == PREFIX_REPNZ && !get_flag(FLAG_ZF))) {
					ecx--;
					eip -= 2;
				}
			}
		} else {
			if (cx || (lock_rep != PREFIX_REPZ && lock_rep != PREFIX_REPNZ)) {
				alu_sub_byte(read_byte(PREFIX_ES, di), read_byte(PREFIX_DS, si));
				if (get_flag(FLAG_DF)) {
					si--;
					di--;
				} else {
					si++;
					di++;
				}
				if ((lock_rep == PREFIX_REPZ && get_flag(FLAG_ZF)) || (lock_rep == PREFIX_REPNZ && !get_flag(FLAG_ZF))) {
					cx--;
					eip -= 2;
				}
			}
		}
		return;
	case 0xa7:
		/* cmpsd */
		if (oper_use32) {
			if (addr_use32) {
				if (ecx || (lock_rep != PREFIX_REPZ && lock_rep != PREFIX_REPNZ)) {
					alu_sub_dword(read_dword(PREFIX_ES, edi), read_dword(PREFIX_DS, esi));
					if (get_flag(FLAG_DF)) {
						esi -= 4;
						edi -= 4;
					} else {
						esi += 4;
						edi += 4;
					}
					if ((lock_rep == PREFIX_REPZ && get_flag(FLAG_ZF)) || (lock_rep == PREFIX_REPNZ && !get_flag(FLAG_ZF))) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || (lock_rep != PREFIX_REPZ && lock_rep != PREFIX_REPNZ)) {
					alu_sub_dword(read_dword(PREFIX_ES, di), read_dword(PREFIX_DS, si));
					if (get_flag(FLAG_DF)) {
						si -= 4;
						di -= 4;
					} else {
						si += 4;
						di += 4;
					}
					if ((lock_rep == PREFIX_REPZ && get_flag(FLAG_ZF)) || (lock_rep == PREFIX_REPNZ && !get_flag(FLAG_ZF))) {
						cx--;
						eip -= 2;
					}
				}
			}
		/* cmpsw */
		} else {
			if (addr_use32) {
				if (ecx || (lock_rep != PREFIX_REPZ && lock_rep != PREFIX_REPNZ)) {
					alu_sub_word(read_word(PREFIX_ES, edi), read_word(PREFIX_DS, esi));
					if (get_flag(FLAG_DF)) {
						esi -= 2;
						edi -= 2;
					} else {
						esi += 2;
						edi += 2;
					}
					if ((lock_rep == PREFIX_REPZ && get_flag(FLAG_ZF)) || (lock_rep == PREFIX_REPNZ && !get_flag(FLAG_ZF))) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || (lock_rep != PREFIX_REPZ && lock_rep != PREFIX_REPNZ)) {
					alu_sub_word(read_word(PREFIX_ES, di), read_word(PREFIX_DS, si));
					if (get_flag(FLAG_DF)) {
						si -= 2;
						di -= 2;
					} else {
						si += 2;
						di += 2;
					}
					if ((lock_rep == PREFIX_REPZ && get_flag(FLAG_ZF)) || (lock_rep == PREFIX_REPNZ && !get_flag(FLAG_ZF))) {
						cx--;
						eip -= 2;
					}
				}
			}
		}
		return;
	case 0xa8:
		/* test (alternate encoding) */
		alu_and_byte(al, execute(1));
		return;
	case 0xa9:
		/* test (alternate encoding) */
		if (oper_use32)
			alu_and_dword(eax, execute(4));
		else
			alu_and_word(ax, execute(2));
		return;
	case 0xaa:
		/* stosb */
		if (addr_use32) {
			if (ecx || lock_rep != PREFIX_REPZ) {
				write_byte(PREFIX_ES, edi, al);
				if (get_flag(FLAG_DF))
					edi--;
				else
					edi++;
				if (lock_rep == PREFIX_REPZ) {
					ecx--;
					eip -= 2;
				}
			}
		} else {
			if (cx || lock_rep != PREFIX_REPZ) {
				write_byte(PREFIX_ES, di, al);
				if (get_flag(FLAG_DF))
					di--;
				else
					di++;
				if (lock_rep == PREFIX_REPZ) {
					cx--;
					eip -= 2;
				}
			}
		}
		return;
	case 0xab:
		/* stosd */
		if (oper_use32) {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					write_dword(PREFIX_ES, edi, eax);
					if (get_flag(FLAG_DF))
						edi -= 4;
					else
						edi += 4;
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					write_dword(PREFIX_ES, di, eax);
					if (get_flag(FLAG_DF))
						di -= 4;
					else
						di += 4;
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		/* stosw */
		} else {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					write_word(PREFIX_ES, edi, ax);
					if (get_flag(FLAG_DF))
						edi -= 2;
					else
						edi += 2;
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					write_word(PREFIX_ES, di, ax);
					if (get_flag(FLAG_DF))
						di -= 2;
					else
						di += 2;
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		}
		return;
	case 0xac:
		/* lodsb */
		if (addr_use32) {
			if (ecx || lock_rep != PREFIX_REPZ) {
				al = read_byte(PREFIX_DS, esi);
				if (get_flag(FLAG_DF))
					esi--;
				else
					esi++;
				if (lock_rep == PREFIX_REPZ) {
					ecx--;
					eip -= 2;
				}
			}
		} else {
			if (cx || lock_rep != PREFIX_REPZ) {
				al = read_byte(PREFIX_DS, si);
				if (get_flag(FLAG_DF))
					si--;
				else
					si++;
				if (lock_rep == PREFIX_REPZ) {
					cx--;
					eip -= 2;
				}
			}
		}
		return;
	case 0xad:
		/* lodsd */
		if (oper_use32) {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					eax = read_dword(PREFIX_DS, esi);
					if (get_flag(FLAG_DF))
						esi -= 4;
					else
						esi += 4;
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					eax = read_dword(PREFIX_DS, si);
					if (get_flag(FLAG_DF))
						si -= 4;
					else
						si += 4;
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		/* lodsw */
		} else {
			if (addr_use32) {
				if (ecx || lock_rep != PREFIX_REPZ) {
					ax = read_word(PREFIX_DS, esi);
					if (get_flag(FLAG_DF))
						esi -= 2;
					else
						esi += 2;
					if (lock_rep == PREFIX_REPZ) {
						ecx--;
						eip -= 2;
					}
				}
			} else {
				if (cx || lock_rep != PREFIX_REPZ) {
					ax = read_word(PREFIX_DS, si);
					if (get_flag(FLAG_DF))
						si -= 2;
					else
						si += 2;
					if (lock_rep == PREFIX_REPZ) {
						cx--;
						eip -= 2;
					}
				}
			}
		}
		return;
	case 0xb0:	case 0xb1:	case 0xb2:	case 0xb3:	case 0xb4:	case 0xb5:	case 0xb6:	case 0xb7:
		/* move immediate to register */
		reg8(op) = execute(1);
		return;
	case 0xb8:	case 0xb9:	case 0xba:	case 0xbb:	case 0xbc:	case 0xbd:	case 0xbe:	case 0xbf:
		/* move immediate to register */
		if (oper_use32)
			reg32(op) = execute(4);
		else
			reg16(op) = execute(2);
		return;
	case 0xc0:
		/* shift by immediate count */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			reg8(modrm) = process_group2_byte(modrm >> 3, reg8(modrm), execute(1));
		} else {
			addr = process_modrm(modrm);
			write_byte(override, addr, process_group2_byte(modrm >> 3, read_byte(override, addr), execute(1)));
		}
		return;
	case 0xc1:
		/* shift by immediate count */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm) = process_group2_dword(modrm >> 3, reg32(modrm), execute(1));
			else
				reg16(modrm) = process_group2_word(modrm >> 3, reg16(modrm), execute(1));
		} else {
			addr = process_modrm(modrm);
			if (oper_use32)
				write_dword(override, addr, process_group2_dword(modrm >> 3, read_dword(override, addr), execute(1)));
			else
				write_word(override, addr, process_group2_word(modrm >> 3, read_word(override, addr), execute(1)));
		}
		return;
	case 0xc2:	case 0xc3:
		/* near return */
		new_eip = oper_use32 ? pop_dword() : (pop_dword() & 0x0000ffff);
		if (!(op & 0x01)) {
			offset = execute(2);
			if (stack_use32)
				esp += offset;
			else
				sp += offset;
		}
		absolute_jump(new_eip);
		return;
	case 0xc6:
		modrm = execute(1);
		switch ((modrm >> 3) & 0x07) {
		case 0x00:
			/* move */
			if ((modrm & 0xc0) == 0xc0) {
				reg8(modrm) = execute(1);
			} else {
				addr = process_modrm(modrm);
				write_byte(override, addr, execute(1));
			}
			return;
		}
		assert(0);
		return;
	case 0xc7:
		modrm = execute(1);
		switch ((modrm >> 3) & 0x07) {
		case 0x00:
			/* move */
			if ((modrm & 0xc0) == 0xc0) {
				if (oper_use32)
					reg32(modrm) = execute(4);
				else
					reg16(modrm) = execute(2);
			} else {
				addr = process_modrm(modrm);
				if (oper_use32)
					write_dword(override, addr, execute(4));
				else
					write_word(override, addr, execute(2));
			}
			return;
		}
		assert(0);
		return;
	case 0xc8:
		/* enter */
		assert(0);
		return;
	case 0xc9:
		/* leave */
		if (stack_use32)
			esp = ebp;
		else
			sp = bp;
		if (oper_use32)
			ebp = pop_dword();
		else
			bp = pop_word();
		return;
	case 0xcc:
		/* int3 */
		raise_exception(EX_BP);
		return;
	case 0xcd:
		/* int */
		raise_exception(execute(1));
		return;
	case 0xce:
		/* into */
		raise_exception(EX_OF);
		return;
	case 0xd0:
		/* shift by 1 */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			reg8(modrm) = process_group2_byte(modrm >> 3, reg8(modrm), 1);
		} else {
			addr = process_modrm(modrm);
			write_byte(override, addr, process_group2_byte(modrm >> 3, read_byte(override, addr), 1));
		}
		return;
	case 0xd1:
		/* shift by 1 */
		modrm = execute(1);
		if (oper_use32) {
			if ((modrm & 0xc0) == 0xc0) {
				reg32(modrm) = process_group2_dword(modrm >> 3, reg32(modrm), 1);
			} else {
				addr = process_modrm(modrm);
				write_dword(override, addr, process_group2_dword(modrm >> 3, read_dword(override, addr), 1));
			}
		} else {
			if ((modrm & 0xc0) == 0xc0) {
				reg16(modrm) = process_group2_word(modrm >> 3, reg16(modrm), 1);
			} else {
				addr = process_modrm(modrm);
				write_word(override, addr, process_group2_word(modrm >> 3, read_word(override, addr), 1));
			}
		}
		return;
	case 0xd2:
		/* shift by CL */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			reg8(modrm) = process_group2_byte(modrm >> 3, reg8(modrm), cl);
		} else {
			addr = process_modrm(modrm);
			write_byte(override, addr, process_group2_byte(modrm >> 3, read_byte(override, addr), cl));
		}
		return;
	case 0xd3:
		/* shift by CL */
		modrm = execute(1);
		if (oper_use32) {
			if ((modrm & 0xc0) == 0xc0) {
				reg32(modrm) = process_group2_dword(modrm >> 3, reg32(modrm), cl);
			} else {
				addr = process_modrm(modrm);
				write_dword(override, addr, process_group2_dword(modrm >> 3, read_dword(override, addr), cl));
			}
		} else {
			if ((modrm & 0xc0) == 0xc0) {
				reg16(modrm) = process_group2_word(modrm >> 3, reg16(modrm), cl);
			} else {
				addr = process_modrm(modrm);
				write_word(override, addr, process_group2_word(modrm >> 3, read_word(override, addr), cl));
			}
		}
		return;
	case 0xe0:
		/* loopnz */
		disp = (int8)execute(1);
		if (!get_flag(FLAG_ZF) && (addr_use32 ? --ecx : --cx))
			relative_jump(disp);
		return;
	case 0xe1:
		/* loopz */
		disp = (int8)execute(1);
		if (get_flag(FLAG_ZF) && (addr_use32 ? --ecx : --cx))
			relative_jump(disp);
		return;
	case 0xe2:
		/* loop */
		disp = (int8)execute(1);
		if (addr_use32 ? --ecx : --cx)
			relative_jump(disp);
		return;
	case 0xe3:
		/* jcxz/jecxz */
		if (!ecx || (!addr_use32 && !cx))
			relative_jump((int8)execute(1));
		return;
	case 0xe4:
		/* in from fixed port */
		check_port_access();
		al = in_byte(execute(1));
		return;
	case 0xe5:
		/* in from fixed port */
		check_port_access();
		if (oper_use32)
			eax = in_dword(execute(1));
		else
			ax = in_word(execute(1));
		return;
	case 0xe6:
		/* out to fixed port */
		check_port_access();
		out_byte(execute(1), al);
		return;
	case 0xe7:
		/* out to fixed port */
		check_port_access();
		if (oper_use32)
			out_dword(execute(1), eax);
		else
			out_word(execute(1), ax);
		return;
	case 0xe8:
		/* near call */
		if (oper_use32) {
			offset = (int32)execute(4);
			push_dword(eip);
		} else {
			offset = (int16)execute(2);
			push_word(eip);
		}
		relative_jump(offset);
		return;
	case 0xe9:
		/* near jump without condition */
		relative_jump(oper_use32 ? (int32)execute(4) : (int16)execute(2));
		return;
	case 0xea:
		/* far jump without condition */
		new_eip = oper_use32 ? execute(4) : execute(2);
		load_segment(SEG_CS, execute(2));
		absolute_jump(new_eip);
		return;
	case 0xeb:
		/* short jump without condition */
		relative_jump((int8)execute(1));
		return;
	case 0xec:
		/* in from variable port */
		check_port_access();
		al = in_byte(dx);
		return;
	case 0xed:
		/* in from variable port */
		check_port_access();
		if (oper_use32)
			eax = in_dword(dx);
		else
			ax = in_word(dx);
		return;
	case 0xee:
		/* out to variable port */
		check_port_access();
		out_byte(dx, al);
		return;
	case 0xef:
		/* out to variable port */
		check_port_access();
		if (oper_use32)
			out_dword(dx, eax);
		else
			out_word(dx, ax);
		return;
	case 0xf4:
		/* hlt */
		assert(0);
		return;
	case 0xf6:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			reg8(modrm) = process_group3_byte(modrm >> 3, reg8(modrm));
		} else {
			addr = process_modrm(modrm);
			org = read_byte(override, addr);
			new = process_group3_byte(modrm >> 3, org);
			if (new != org)
				write_byte(override, addr, new);
		}
		return;
	case 0xf7:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm) = process_group3_dword(modrm >> 3, reg32(modrm));
			else
				reg16(modrm) = process_group3_word(modrm >> 3, reg16(modrm));
		} else {
			addr = process_modrm(modrm);
			if (oper_use32) {
				org = read_dword(override, addr);
				new = process_group3_dword(modrm >> 3, org);
				if (new != org)
					write_dword(override, addr, new);
			} else {
				org = read_word(override, addr);
				new = process_group3_word(modrm >> 3, org);
				if (new != org)
					write_word(override, addr, new);
			}
		}
		return;
	case 0xf8:
		/* clc */
		set_flag(FLAG_CF, false);
		return;
	case 0xf9:
		/* stc */
		set_flag(FLAG_CF, true);
		return;
	case 0xfa:
		/* cli */
		check_if_access();
		set_flag(FLAG_IF, false);
		return;
	case 0xfb:
		/* sti */
		check_if_access();
		set_flag(FLAG_IF, true);
		return;
	case 0xfc:
		/* cld */
		set_flag(FLAG_DF, false);
		return;
	case 0xfd:
		/* std */
		set_flag(FLAG_DF, true);
		return;
	case 0xfe:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			reg8(modrm) = process_group4_byte(modrm >> 3, reg8(modrm));
		} else {
			addr = process_modrm(modrm);
			write_byte(override, addr, process_group4_byte(modrm >> 3, read_byte(override, addr)));
		}
		return;
	case 0xff:
		modrm = execute(1);
		switch ((modrm >> 3) & 0x07) {
		case 0x00:
			/* inc */
			if (oper_use32) {
				if ((modrm & 0xc0) == 0xc0) {
					reg32(modrm) = alu_inc_dword(reg32(modrm));
				} else {
					addr = process_modrm(modrm);
					write_dword(override, addr, alu_inc_dword(read_dword(override, addr)));
				}
			} else {
				if ((modrm & 0xc0) == 0xc0) {
					reg16(modrm) = alu_inc_word(reg16(modrm));
				} else {
					addr = process_modrm(modrm);
					write_word(override, addr, alu_inc_word(read_word(override, addr)));
				}
			}
			return;
		case 0x01:
			/* dec */
			if (oper_use32) {
				if ((modrm & 0xc0) == 0xc0) {
					reg32(modrm) = alu_dec_dword(reg32(modrm));
				} else {
					addr = process_modrm(modrm);
					write_dword(override, addr, alu_dec_dword(read_dword(override, addr)));
				}
			} else {
				if ((modrm & 0xc0) == 0xc0) {
					reg16(modrm) = alu_dec_word(reg16(modrm));
				} else {
					addr = process_modrm(modrm);
					write_word(override, addr, alu_dec_word(read_word(override, addr)));
				}
			}
			return;
		case 0x02:
			/* near call */
			if (oper_use32) {
				if ((modrm & 0xc0) == 0xc0)
					new_eip = reg32(modrm);
				else
					new_eip = read_dword(override, process_modrm(modrm));
				push_dword(eip);
			} else {
				if ((modrm & 0xc0) == 0xc0)
					new_eip = reg16(modrm);
				else
					new_eip = read_word(override, process_modrm(modrm));
				push_word(eip);
			}
			absolute_jump(new_eip);
			return;
		case 0x03:
			/* far call */
			if (oper_use32) {
				if ((modrm & 0xc0) == 0xc0) {
					assert(0);
				} else {
					addr = process_modrm(modrm);
					new_eip = read_dword(override, addr);
					addr += 4;
					load_segment(SEG_CS, read_word(override, addr));
					absolute_jump(new_eip);
				}
				push_dword(eip);
			} else {
				if ((modrm & 0xc0) == 0xc0) {
					assert(0);
				} else {
					addr = process_modrm(modrm);
					new_eip = read_word(override, addr);
					addr += 2;
					load_segment(SEG_CS, read_word(override, addr));
				}
				push_word(eip);
			}
			absolute_jump(new_eip);
			return;
		case 0x04:
			/* near jump without condition */
			if ((modrm & 0xc0) == 0xc0) {
				absolute_jump(addr_use32 ? reg32(modrm) : reg16(modrm));
			} else {
				addr = process_modrm(modrm);
				absolute_jump(addr_use32 ? read_dword(override, addr) : read_word(override, addr));
			}
			return;
		case 0x05:
			if ((modrm & 0xc0) == 0xc0) {
				assert(0);
			} else {
				/* far jump without condition */
				addr = process_modrm(modrm);
				if (addr_use32) {
					new_eip = read_dword(override, addr);
					addr += 4;
				} else {
					new_eip = read_word(override, addr);
					addr += 2;
				}
				load_segment(SEG_CS, read_word(override, addr));
				absolute_jump(new_eip);
			}
			return;
		case 0x06:
			/* push */
			if ((modrm & 0xc0) == 0xc0) {
				if (oper_use32)
					push_dword(reg32(modrm));
				else
					push_word(reg32(modrm));
			} else {
				addr = process_modrm(modrm);
				if (oper_use32)
					push_dword(read_dword(override, addr));
				else
					push_word(read_word(override, addr));
			}
			return;
		}
		assert(0);
		return;
	}

	raise_exception(EX_UD);
}

static uint32 fastcall process_modrm(uint modrm)
{
	int prefix = PREFIX_DS;
	uint8 mod = modrm & 0xc0;
	uint32 addr;

	if (addr_use32) {

		switch (modrm & 7) {
		case 0x00:
			addr = eax;
			break;
		case 0x01:
			addr = ecx;
			break;
		case 0x02:
			addr = edx;
			break;
		case 0x03:
			addr = ebx;
			break;
		case 0x04:
			addr = process_sib(execute(1), mod);
			break;
		case 0x05:
			if (!mod) {
				addr = execute(4);
				break;
			}
			addr = ebp;
			prefix = PREFIX_SS;
			break;
		case 0x06:
			addr = esi;
			break;
		case 0x07:
			addr = edi;
			break;
		}

		switch (mod) {
		case 0x40:
			addr += (int8)execute(1);
			break;
		case 0x80:
			addr += execute(4);
			break;
		}

	} else {

		switch (modrm & 7) {
		case 0x00:
			addr = ebx + esi;
			break;
		case 0x01:
			addr = ebx + edi;
			break;
		case 0x02:
			addr = ebp + esi;
			prefix = PREFIX_SS;
			break;
		case 0x03:
			addr = ebp + edi;
			prefix = PREFIX_SS;
			break;
		case 0x04:
			addr = esi;
			break;
		case 0x05:
			addr = edi;
			break;
		case 0x06:
			if (!mod) {
				addr = execute(2);
				break;
			}
			addr = ebp;
			prefix = PREFIX_SS;
			break;
		case 0x07:
			addr = ebx;
			break;
		}

		switch (mod) {
		case 0x40:
			addr += (int8)execute(1);
			break;
		case 0x80:
			addr += execute(2);
			break;
		}

		addr &= 0x0000ffff;
	}

	/* The default segment register is SS for the effective addresses containing a BP/EBP index, */
	/* DS for other effective addresses. */
	if (override == PREFIX_NONE)
		override = prefix;

	return addr;
}

static void fastcall process_ext_ins(uint op)
{
	int no, offset;
	uint modrm;
	uint8 imm, *bp1, *bp2;
	uint16 sel, limit;
	uint32 addr, base, tmp;

	switch (op) {
	case 0x00:
		modrm = execute(1);
		switch ((modrm >> 3) & 0x07) {
		case 0x00:
			/* sldt */
			assert(0);
			return;
		case 0x01:
			/* str */
			if (!(cr0 & CR0_PE) || get_flag(FLAG_VM))
				raise_exception(EX_UD);
			if ((modrm & 0xc0) == 0xc0) {
				reg16(modrm) = tr;
			} else {
				addr = process_modrm(modrm);
				write_word(override, addr, tr);
			}
			return;
		case 0x02:
			/* lldt */
			assert(0);
			return;
		case 0x03:
			/* ltr */
			if (!(cr0 & CR0_PE) || get_flag(FLAG_VM))
				raise_exception(EX_UD);
			if ((modrm & 0xc0) == 0xc0) {
				sel = reg16(modrm);
			} else {
				addr = process_modrm(modrm);
				sel = read_word(override, addr);
			}
			load_task(sel);
			return;
		}
		raise_exception(EX_UD);
		return;
	case 0x01:
		modrm = execute(1);
		if ((modrm & 0xc0) != 0xc0) {
			addr = process_modrm(modrm);
			switch ((modrm >> 3) & 0x07) {
			case 0x00:
				/* sgdt */
				assert(0);
				return;
			case 0x01:
				/* sidt */
				assert(0);
				return;
			case 0x02:
				/* lgdt */
				check_lxdt_access();
				limit = read_word(override, addr);
				base = read_dword(override, addr + 2);
				if (!oper_use32)
					base &= 0x00ffffff;
				gdtr = make_dtr(limit, base);
				return;
			case 0x03:
				/* lidt */
				check_lxdt_access();
				limit = read_word(override, addr);
				base = read_dword(override, addr + 2);
				if (!oper_use32)
					base &= 0x00ffffff;
				idtr = make_dtr(limit, base);
				return;
			case 0x07:
				/* invlpg */
#ifndef NDEBUG
				write_log("[CPU] INVLPG");
#endif
				return;
			}
		}
		raise_exception(EX_UD);
		return;
	case 0x09:
		/* wbinvd */
#ifndef NDEBUG
		write_log("[CPU] WBINVD");
#endif
		// TODO:
		return;
	case 0x20:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			/* move from control register */
			no = (modrm >> 3) & 0x07;
			check_cr_access(no);
			reg32(modrm) = crx[no];
		} else {
			assert(0);
		}
		return;
	case 0x21:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			/* move from debug register */
			no = (modrm >> 3) & 0x07;
			check_dr_access(no);
			reg32(modrm) = drx[no];
		} else {
			assert(0);
		}
		return;
	case 0x22:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			/* move to control register */
			no = (modrm >> 3) & 0x07;
			check_cr_access(no);
			crx[no] = reg32(modrm);
		} else {
			assert(0);
		}
		return;
	case 0x23:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			/* move debug register */
			no = (modrm >> 3) & 0x07;
			check_dr_access(no);
			drx[no] = reg32(modrm);
		} else {
			assert(0);
		}
		return;
	case 0x30:
		/* wrmsr */
		check_msr_access(ecx);
#ifndef NDEBUG
		write_log("[CPU] WRMSR %04Xh = %08X%08Xh", ecx, edx, eax);
#endif
		// TODO:
		return;
	case 0x31:
		/* rdtsc */
#ifndef NDEBUG
		write_log("[CPU] RDTSC");
#endif
		check_tsc_access();
		// TODO:
		eax = (reg32_t)tsc;
		edx = (reg32_t)(tsc >> 32);
		return;
	case 0x32:
		/* rdmsr */
		check_msr_access(ecx);
#ifndef NDEBUG
		write_log("[CPU] RDMSR %04Xh = %08X%08Xh", ecx, edx, eax);
#endif
		// TODO:
		return;
	case 0x33:
		/* rdpmc */
		assert(0);
#ifndef NDEBUG
		write_log("[CPU] RDPMC");
#endif
		// TODO:
		return;
	case 0x80:	case 0x81:	case 0x82:	case 0x83:	case 0x84:	case 0x85:	case 0x86:	case 0x87:
	case 0x88:	case 0x89:	case 0x8a:	case 0x8b:	case 0x8c:	case 0x8d:	case 0x8e:	case 0x8f:
		/* near jump with condition */
		offset = oper_use32 ? (int32)execute(4) : (int16)execute(2);
		if (test_cond(op))
			relative_jump(offset);
		return;
	case 0x90:	case 0x91:	case 0x92:	case 0x93:	case 0x94:	case 0x95:	case 0x96:	case 0x97:
	case 0x98:	case 0x99:	case 0x9a:	case 0x9b:	case 0x9c:	case 0x9d:	case 0x9e:	case 0x9f:
		/* byte set on condition */
		modrm = execute(1);
		switch ((modrm >> 3) & 0x07) {
		case 0x00:
			if ((modrm & 0xc0) == 0xc0) {
				reg8(modrm) = test_cond(op) ? 0x01 : 0x00;
			} else {
				addr = process_modrm(modrm);
				write_byte(override, addr, test_cond(op) ? 0x01 : 0x00);
			}
			return;
		}
		raise_exception(EX_UD);
		return;
	case 0xa0:	case 0xa8:
		/* push segment register */
		push_dword(sreg3(op >> 3));
		return;
	case 0xa1:	case 0xa9:
		/* pop segment register */
		load_segment((op >> 3) & 7, pop_dword());
		return;
	case 0xa2:
		/* cpuid */
		switch (eax) {
		case 0:
			eax = 0x00000002;	/* P6 family processors */
			ebx = 0x756e6547;	/* "Genu" */
			ecx = 0x6c65746e;	/* "ntel" */
			edx = 0x49656e69;	/* "ineI" */
			return;
		case 1:
			eax = 0x0000068a;	/* family 6, model 8, stepping 10 */
			ebx = 0x00000000;
			ecx = 0x00000000;
			edx = 0x0383f9ff;	/* feature flags */
			return;
		case 2:
			eax = 0x03020101;
			ebx = 0x00000000;
			ecx = 0x00000000;
			edx = 0x0c040841;
			return;
		}
		return;
	case 0xa3:	case 0xab:	case 0xb3:	case 0xbb:
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm) = process_group8_dword(op >> 3, reg32(modrm), reg8(modrm >> 3));
			else
				reg16(modrm) = process_group8_word(op >> 3, reg16(modrm), reg8(modrm >> 3));
		} else {
			addr = process_modrm(modrm);
			if (oper_use32) {
				base = read_dword(override, addr);
				tmp = process_group8_dword(op >> 3, base, reg8(modrm >> 3));
				if (tmp != base)
					write_dword(override, addr, tmp);
			} else {
				base = read_word(override, addr);
				tmp = process_group8_dword(op >> 3, base, reg8(modrm >> 3));
				if (tmp != base)
					write_word(override, addr, tmp);
			}
		}
		return;
	case 0xaf:
		/* imul register with register/memory */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm >> 3) = (reg32_t)alu_imul_dword(reg32(modrm >> 3), reg32(modrm), &tmp);
			else
				reg16(modrm >> 3) = (reg16_t)alu_imul_word(reg16(modrm >> 3), reg16(modrm), (int16 *)&tmp);
		} else {
			addr = process_modrm(modrm);
			if (oper_use32)
				reg32(modrm >> 3) = (reg32_t)alu_imul_dword(reg32(modrm >> 3), read_dword(override, addr), &tmp);
			else
				reg16(modrm >> 3) = (reg16_t)alu_imul_word(reg16(modrm >> 3), read_word(override, addr), (int16 *)&tmp);
		}
		return;
	case 0xb6:
		/* move with zero-extend */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0)
			tmp = (uint8)reg8(modrm);
		else
			tmp = (uint8)read_byte(override, process_modrm(modrm));
		if (oper_use32)
			reg32(modrm >> 3) = tmp;
		else
			reg16(modrm >> 3) = tmp;
		return;
	case 0xb7:
		/* move with zero-extend */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0)
			reg32(modrm >> 3) = (uint16)reg16(modrm);
		else
			reg32(modrm >> 3) = (uint16)read_word(override, process_modrm(modrm));
		return;
	case 0xba:
		modrm = execute(1);
		imm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32)
				reg32(modrm) = process_group8_dword(modrm >> 3, reg32(modrm), imm);
			else
				reg16(modrm) = process_group8_word(modrm >> 3, reg16(modrm), imm);
		} else {
			addr = process_modrm(modrm);
			if (oper_use32) {
				base = read_dword(override, addr);
				tmp = process_group8_dword(op >> 3, base, imm);
				if (tmp != base)
					write_dword(override, addr, tmp);
			} else {
				base = read_word(override, addr);
				tmp = process_group8_dword(op >> 3, base, imm);
				if (tmp != base)
					write_word(override, addr, tmp);
			}
		}
		return;
	case 0xbc:
		/* bsf */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32) {
				tmp = reg32(modrm);
				if (tmp) {
					set_flag(FLAG_ZF, false);
					for (offset = 31; offset > 0; offset--) {
						if (tmp & 0x80000000)
							break;
						tmp <<= 1;
					}
					reg32(modrm >> 3) = offset;
				} else {
					set_flag(FLAG_ZF, true);
				}
			} else {
				tmp = reg16(modrm);
				if (tmp) {
					set_flag(FLAG_ZF, false);
					for (offset = 15; offset > 0; offset--) {
						if (tmp & 0x8000)
							break;
						tmp <<= 1;
					}
					reg16(modrm >> 3) = offset;
				} else {
					set_flag(FLAG_ZF, true);
				}
			}
		} else {
			addr = process_modrm(modrm);
			if (oper_use32) {
				tmp = read_dword(override, addr);
				if (tmp) {
					set_flag(FLAG_ZF, false);
					for (offset = 31; offset > 0; offset--) {
						if (tmp & 0x80000000)
							break;
						tmp <<= 1;
					}
					reg32(modrm >> 3) = offset;
				} else {
					set_flag(FLAG_ZF, true);
				}
			} else {
				tmp = read_word(override, addr);
				if (tmp) {
					set_flag(FLAG_ZF, false);
					for (offset = 15; offset > 0; offset--) {
						if (tmp & 0x8000)
							break;
						tmp <<= 1;
					}
					reg16(modrm >> 3) = offset;
				} else {
					set_flag(FLAG_ZF, true);
				}
			}
		}
		return;
	case 0xbd:
		/* bsr */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32) {
				tmp = reg32(modrm);
				if (tmp) {
					set_flag(FLAG_ZF, false);
					for (offset = 0; offset < 31; offset++) {
						if (tmp & 0x00000001)
							break;
						tmp >>= 1;
					}
					reg32(modrm >> 3) = offset;
				} else {
					set_flag(FLAG_ZF, true);
				}
			} else {
				tmp = reg16(modrm);
				if (tmp) {
					set_flag(FLAG_ZF, false);
					for (offset = 0; offset < 15; offset++) {
						if (tmp & 0x0001)
							break;
						tmp >>= 1;
					}
					reg16(modrm >> 3) = offset;
				} else {
					set_flag(FLAG_ZF, true);
				}
			}
		} else {
			addr = process_modrm(modrm);
			if (oper_use32) {
				tmp = read_dword(override, addr);
				if (tmp) {
					set_flag(FLAG_ZF, false);
					for (offset = 0; offset < 31; offset++) {
						if (tmp & 0x00000001)
							break;
						tmp >>= 1;
					}
					reg32(modrm >> 3) = offset;
				} else {
					set_flag(FLAG_ZF, true);
				}
			} else {
				tmp = read_word(override, addr);
				if (tmp) {
					set_flag(FLAG_ZF, false);
					for (offset = 0; offset < 15; offset++) {
						if (tmp & 0x0001)
							break;
						tmp >>= 1;
					}
					reg16(modrm >> 3) = offset;
				} else {
					set_flag(FLAG_ZF, true);
				}
			}
		}
		return;
	case 0xbe:
		/* move with sign-extend */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0)
			tmp = (int8)reg8(modrm);
		else
			tmp = (int8)read_byte(override, process_modrm(modrm));
		if (oper_use32)
			reg32(modrm >> 3) = tmp;
		else
			reg16(modrm >> 3) = tmp;
		return;
	case 0xbf:
		/* move with sign-extend */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0)
			reg32(modrm >> 3) = (int16)reg16(modrm);
		else
			reg32(modrm >> 3) = (int16)read_word(override, process_modrm(modrm));
		return;
	case 0xc0:
		/* xadd */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			tmp = reg8(modrm >> 3);
			reg8(modrm >> 3) = reg8(modrm);
			reg8(modrm) = alu_add_byte(tmp, reg8(modrm >> 3));
		} else {
			addr = process_modrm(modrm);
			tmp = reg8(modrm >> 3);
			reg8(modrm >> 3) = read_byte(override, addr);
			write_byte(override, addr, alu_add_byte(tmp, reg8(modrm >> 3)));
		}
		return;
	case 0xc1:
		/* xadd */
		modrm = execute(1);
		if ((modrm & 0xc0) == 0xc0) {
			if (oper_use32) {
				tmp = reg32(modrm >> 3);
				reg32(modrm >> 3) = reg32(modrm);
				reg32(modrm) = alu_add_dword(tmp, reg32(modrm >> 3));
			} else {
				tmp = reg16(modrm >> 3);
				reg16(modrm >> 3) = reg16(modrm);
				reg16(modrm) = alu_add_word(tmp, reg16(modrm >> 3));
			}
		} else {
			if (oper_use32) {
				addr = process_modrm(modrm);
				tmp = reg32(modrm >> 3);
				reg32(modrm >> 3) = read_dword(override, addr);
				write_dword(override, addr, alu_add_dword(tmp, reg32(modrm >> 3)));
			} else {
				addr = process_modrm(modrm);
				tmp = reg16(modrm >> 3);
				reg16(modrm >> 3) = read_word(override, addr);
				write_word(override, addr, alu_add_word(tmp, reg16(modrm >> 3)));
			}
		}
		return;
	case 0xc8:	case 0xc9:	case 0xca:	case 0xcb:	case 0xcc:	case 0xcd:	case 0xce:	case 0xcf:
		/* bswap */
		bp1 = &reg8(op);
		bp2 = &reg8(op) + 3;
		tmp = *bp1;
		*bp1 = *bp2;
		*bp2 = tmp;
		bp1 = &reg8(op) + 1;
		bp2 = &reg8(op) + 2;
		tmp = *bp1;
		*bp1 = *bp2;
		*bp2 = tmp;
		return;
	}

	raise_exception(EX_UD);
}

static void fastcall load_segment(int seg, uint16 sel)
{
	uint32 la, pa;
	uint64 desc;

	/* real-address or virtual-8086 mode */
	if (!(cr0 & CR0_PE) || get_flag(FLAG_VM)) {
		sreg[seg] = sel;
		return;
	}

	/* protected mode only */
	la = (uint32)(gdtr >> 16) + sel_index(sel);
	pa = paging(la);
	desc = memory_read(pa, 4);
	desc |= (uint64)(memory_read(pa + 4, 4)) << 32;

	switch (seg) {
	case SEG_CS:
		check_cs_access(sreg[seg], xsreg[seg], sel, desc);
		sel = sel_index(sel) | sel_cpl(cs);
		break;
	case SEG_SS:
		check_ss_access(sreg[seg], xsreg[seg], sel, desc);
		break;
	default:
		check_ds_access(sreg[seg], xsreg[seg], sel, desc);
		break;
	}

	sreg[seg] = sel;
	xsreg[seg] = desc;
}

static void fastcall load_task(uint16 sel)
{
	uint32 la, pa;
	uint64 desc;

	la = (uint32)(gdtr >> 16) + sel_index(sel);
	pa = paging(la);
	desc = memory_read(pa, 4);
	desc |= (uint64)(memory_read(pa + 4, 4)) << 32;

	check_ltr_access(sel, desc);

	desc |= DESC_B;
	memory_write(pa + 4, (uint32)(desc >> 32), 4);

	tr = sel;
	xtr = desc;
}

static void process_intr(void)
{

}

/* ------------------------------------------------------------------------- */
